﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using SkinnedModel;
using TangoGames.Primitives3D;
using TangoGames.Scene;


namespace TangoGames.Base
{
    public class AnimatedModel:GameObject3D
    {
        #region Fields

        /// <summary>
        /// Modelo base Sem Animação
        /// </summary>
        protected Model currentModel;

        //protected AnimatedModel animation; 

        /// <summary>
        /// Dados do Skin do modelo
        /// </summary>
        protected SkinningData skinningData;

        /// <summary>
        /// Instancia do Player de animação
        /// </summary>
        protected AnimationPlayer animationPlayer;

        /// <summary>
        /// Estruturas de colisão
        /// </summary>
        SkinnedSphere[] skinnedSpheres;
        BoundingSphere[] boundingSpheres;

        /// <summary>
        /// Debug das esferas de colisão
        /// </summary>
        private bool showSpheres = false;
        SpherePrimitive spherePrimitive; 

        #endregion

        #region Properties

        /// <summary>
        /// Mostra esferas de colisão
        /// </summary>
        public bool ShowSpheres { get { return showSpheres; } set { showSpheres = value; } }

        #endregion

        public AnimatedModel(Game game, ICamera camera, Model model, String CollisionSpheresFileName)
            : base(game, camera, model)
        {
            currentModel = model;

            // Look up our custom skinning information.
            skinningData = currentModel.Tag as SkinningData;

            if (skinningData == null)
                throw new Exception("This model does not contain a SkinningData tag.");

            boneTransforms = new Matrix[skinningData.BindPose.Count];

            animationPlayer = new AnimationPlayer(skinningData);

            // Load the bounding spheres.
            skinnedSpheres = game.Content.Load<SkinnedSphere[]>(CollisionSpheresFileName);

            boundingSpheres = new BoundingSphere[skinnedSpheres.Length];

            spherePrimitive = new SpherePrimitive(game.GraphicsDevice, 2, 12);

            //lista o nome dos ossos
            //foreach (KeyValuePair<string, int> b in skinningData.BoneIndices)
            //{
            //    Console.WriteLine(b.Key);
            //}

        }

        public override void Update(GameTime gameTime)
        {
            base.UpdateWorld();

            // Tell the animation player to compute the latest bone transform matrices.
            animationPlayer.UpdateBoneTransforms(gameTime.ElapsedGameTime, true);

            // Copy the transforms into our own array, so we can safely modify the values.
            animationPlayer.GetBoneTransforms().CopyTo(boneTransforms, 0);

            //Faz alterações especiais no modelo
            ChangeBonesUpdate();

            // Tell the animation player to recompute the world and skin matrices.
            animationPlayer.UpdateWorldTransforms(this.world , boneTransforms);
            animationPlayer.UpdateSkinTransforms();

            UpdateBoundingSpheres(); 

       }

        /// <summary>
        /// Pode ser sobrecrita
        /// </summary>
        public virtual void ChangeBonesUpdate()
        { 
            //faz nada
            // Modify the transform matrices for the head and upper-left arm bones.
            // Read gamepad inputs.
            //float headRotation = currentGamePadState.ThumbSticks.Left.X;
            //float armRotation = Math.Max(currentGamePadState.ThumbSticks.Left.Y, 0);
            //
            //int headIndex = skinningData.BoneIndices["Head"];
            //int armIndex = skinningData.BoneIndices["L_UpperArm"];
            //
            //boneTransforms[headIndex] = headTransform * boneTransforms[headIndex];
            //boneTransforms[armIndex] = armTransform * boneTransforms[armIndex];
        }

        /// <summary>
        /// Updates the boundingSpheres array to match the current animation state.
        /// </summary>
        void UpdateBoundingSpheres()
        {
            // Create variables to hold min and max xyz values for the model. Initialise them to extremes
            Vector3 modelMax = new Vector3(float.MinValue, float.MinValue, float.MinValue);
            Vector3 modelMin = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);

            // Look up the current world space bone positions.
            Matrix[] worldTransforms = animationPlayer.GetWorldTransforms();

            for (int i = 0; i < skinnedSpheres.Length; i++)
            {
                // Convert the SkinnedSphere description to a BoundingSphere.
                SkinnedSphere source = skinnedSpheres[i];
                Vector3 center = new Vector3(source.OffsetX, source.OffsetY ,source.OffsetZ);
                BoundingSphere sphere = new BoundingSphere(center, source.Radius);

                // Transform the BoundingSphere by its parent bone matrix,
                // and store the result into the boundingSpheres array.
                int boneIndex = skinningData.BoneIndices[source.BoneName];

                boundingSpheres[i] = sphere.Transform(worldTransforms[boneIndex]);

                center = boundingSpheres[i].Center;
                float radius = boundingSpheres[i].Radius;

                Vector3[] points = { Vector3.Up , Vector3.Down, Vector3.Left , Vector3.Right, Vector3.Forward, Vector3.Backward };
                for (int j = 0; j < points.Length ; j++)
                {
                    Vector3 point = center + ( points[j] * radius);
                    modelMin = Vector3.Min(modelMin, point);
                    modelMax = Vector3.Max(modelMax, point);            
                }
            }

            modelBoundingBox = new BoundingBox(modelMin, modelMax);
        }
        
        public override void Draw(GameTime gameTime)
        {
            //GraphicsDevice device = Game.graphics.GraphicsDevice;

            //device.Clear(Color.CornflowerBlue);

            Matrix[] bones = animationPlayer.GetSkinTransforms();

            // Render the skinned mesh.
            foreach (ModelMesh mesh in currentModel.Meshes)
            {
                foreach (SkinnedEffect effect in mesh.Effects)
                {
                    effect.SetBoneTransforms(bones);

                    effect.View = camera.ViewMatrix;
                    effect.Projection = camera.ProjectionMatrix;

                    effect.EnableDefaultLighting();

                    effect.SpecularColor = new Vector3(0.25f);
                    effect.SpecularPower = 16;
                }

                mesh.Draw();

            }


            if (showSpheres)
            {
                DrawBoundingSpheres(camera.ViewMatrix ,camera.ProjectionMatrix);
            }

            #region Desenhar boundingBox

            if (drawBoundingBox)
            {
                modelBoundingBoxbuffers = CreateBoundingBoxBuffers(modelBoundingBox, Game.GraphicsDevice);

                this.DrawBoundingBox();
            }

            #endregion

        }

        public void LoadAnimation(String animatioFileName)
        {
            Model animation = Game.Content.Load<Model>(animatioFileName);

            SkinningData sd = animation.Tag as SkinningData;

            foreach (KeyValuePair<string, AnimationClip> anim in sd.AnimationClips)
            {       
                string nome = anim.Key;
                while (skinningData.AnimationClips.ContainsKey(nome))
                {
                    nome += "X";
                }
                skinningData.AnimationClips.Add(nome, anim.Value);
 
            }

        }

        public void PlayAnimationClip(String clipName)
        { 
            if (!skinningData.AnimationClips.ContainsKey(clipName ))
                throw new Exception("This model does not contain a " + clipName +" Clip Animation.");

            animationPlayer.StartClip(skinningData.AnimationClips[clipName]);
        }

        public bool CollisionDetect(BoundingBox box)
        {
            BoundingSphere sphere;
            return CollisionDetect(box, out sphere);
        }

        public bool CollisionDetect(BoundingBox box, out BoundingSphere sphere )
        {
            sphere = new BoundingSphere();
            if (modelBoundingBox.Intersects(box))
            {
                foreach (BoundingSphere s in boundingSpheres)
                {
                    if (s.Intersects(box))
                    {
                        sphere = s;
                        return true;
                    }
                }
            }
            return false;
        }

        public bool CollisionDetect(Ray ray, out float range)
        {
            Nullable<float> result = ray.Intersects(modelBoundingBox);
            range = 0;
            if (result.HasValue == true)
            {
                range = result.Value;
                foreach (BoundingSphere sphere in boundingSpheres)
                {
                    result = ray.Intersects(sphere);

                    if (result.HasValue == true)
                    {
                        range = result.Value;
                        return true;
                    }
                }

            }

            return false;
        }

        public bool CollisionDetect(Ray ray)
        {
            float range;
            return CollisionDetect(ray, out range);
        }

        /// <summary>
        /// Draws the animated bounding spheres.
        /// </summary>
        void DrawBoundingSpheres(Matrix view, Matrix projection)
        {
            Game.GraphicsDevice.RasterizerState = Wireframe;

            foreach (BoundingSphere sphere in boundingSpheres)
            {
                Matrix world = Matrix.CreateScale(sphere.Radius) *
                               Matrix.CreateTranslation(sphere.Center);

                spherePrimitive.Draw(world, view, projection, Color.White);

            }

            Game.GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;
        }

        static RasterizerState Wireframe = new RasterizerState
        {
            FillMode = FillMode.WireFrame
        };

    }
}
